agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v14 1/3] Add API of sorts for transition table handling in trigger.c 431+ messages / 3 participants [nested] [flat]
* [PATCH v14 1/3] Add API of sorts for transition table handling in trigger.c @ 2022-02-02 19:58 Alvaro Herrera <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Alvaro Herrera @ 2022-02-02 19:58 UTC (permalink / raw) Preparatory patch for further additions in this area, particularly to allow MERGE having separate transition tables for each action. Author: Pavan Deolasee <[email protected]> Reviewed-by: Álvaro Herrera <[email protected]> Discussion: https://postgr.es/m/CABOikdNj+8HEJ5D8tu56mrPkjHVRrBb2_cdKWwpiYNcjXgDw8g@mail.gmail.com Discussion: https://postgr.es/m/[email protected] --- src/backend/commands/trigger.c | 170 +++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 52 deletions(-) diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 1a9c1ac290..23af3f2269 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -94,6 +94,16 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context); +static Tuplestorestate *AfterTriggerGetTransitionTable(int event, + TupleTableSlot *oldslot, + TupleTableSlot *newslot, + TransitionCaptureState *transition_capture); +static void TransitionTableAddTuple(EState *estate, + TransitionCaptureState *transition_capture, + ResultRelInfo *relinfo, + TupleTableSlot *slot, + TupleTableSlot *original_insert_tuple, + Tuplestorestate *tuplestore); static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, TupleTableSlot *oldtup, TupleTableSlot *newtup, @@ -5650,6 +5660,91 @@ AfterTriggerPendingOnRel(Oid relid) return false; } +/* + * Get the transition table for the given event and depending on whether we are + * processing the old or the new tuple. + */ +static Tuplestorestate * +AfterTriggerGetTransitionTable(int event, + TupleTableSlot *oldslot, + TupleTableSlot *newslot, + TransitionCaptureState *transition_capture) +{ + Tuplestorestate *tuplestore = NULL; + bool delete_old_table = transition_capture->tcs_delete_old_table; + bool update_old_table = transition_capture->tcs_update_old_table; + bool update_new_table = transition_capture->tcs_update_new_table; + bool insert_new_table = transition_capture->tcs_insert_new_table; + + /* + * For INSERT events NEW should be non-NULL, for DELETE events OLD + * should be non-NULL, whereas for UPDATE events normally both OLD and + * NEW are non-NULL. But for UPDATE events fired for capturing + * transition tuples during UPDATE partition-key row movement, OLD is + * NULL when the event is for a row being inserted, whereas NEW is + * NULL when the event is for a row being deleted. + */ + Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table && + TupIsNull(oldslot))); + Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table && + TupIsNull(newslot))); + + if (!TupIsNull(oldslot)) + { + Assert(TupIsNull(newslot)); + if (event == TRIGGER_EVENT_DELETE && delete_old_table) + tuplestore = transition_capture->tcs_private->old_tuplestore; + else if (event == TRIGGER_EVENT_UPDATE && update_old_table) + tuplestore = transition_capture->tcs_private->old_tuplestore; + } + else if (!TupIsNull(newslot)) + { + Assert(TupIsNull(oldslot)); + if (event == TRIGGER_EVENT_INSERT && insert_new_table) + tuplestore = transition_capture->tcs_private->new_tuplestore; + else if (event == TRIGGER_EVENT_UPDATE && update_new_table) + tuplestore = transition_capture->tcs_private->new_tuplestore; + } + + return tuplestore; +} + +/* + * Add the given heap tuple to the given tuplestore, applying the conversion + * map if necessary. + * + * If original_insert_tuple is given, we can add that tuple without conversion. + */ +static void +TransitionTableAddTuple(EState *estate, + TransitionCaptureState *transition_capture, + ResultRelInfo *relinfo, + TupleTableSlot *slot, + TupleTableSlot *original_insert_tuple, + Tuplestorestate *tuplestore) +{ + TupleConversionMap *map; + + /* + * Nothing needs to be done if we don't have a tuplestore. + */ + if (tuplestore == NULL) + return; + + if (original_insert_tuple) + tuplestore_puttupleslot(tuplestore, original_insert_tuple); + else if ((map = ExecGetChildToRootMap(relinfo)) != NULL) + { + AfterTriggersTableData *table = transition_capture->tcs_private; + TupleTableSlot *storeslot; + + storeslot = GetAfterTriggersStoreSlot(table, map->outdesc); + execute_attr_map_slot(map->attrMap, slot, storeslot); + tuplestore_puttupleslot(tuplestore, storeslot); + } + else + tuplestore_puttupleslot(tuplestore, slot); +} /* ---------- * AfterTriggerSaveEvent() @@ -5709,68 +5804,39 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, */ if (row_trigger && transition_capture != NULL) { - TupleTableSlot *original_insert_tuple = transition_capture->tcs_original_insert_tuple; - TupleConversionMap *map = ExecGetChildToRootMap(relinfo); - bool delete_old_table = transition_capture->tcs_delete_old_table; - bool update_old_table = transition_capture->tcs_update_old_table; - bool update_new_table = transition_capture->tcs_update_new_table; - bool insert_new_table = transition_capture->tcs_insert_new_table; /* - * For INSERT events NEW should be non-NULL, for DELETE events OLD - * should be non-NULL, whereas for UPDATE events normally both OLD and - * NEW are non-NULL. But for UPDATE events fired for capturing - * transition tuples during UPDATE partition-key row movement, OLD is - * NULL when the event is for a row being inserted, whereas NEW is - * NULL when the event is for a row being deleted. + * Capture the old tuple in the appropriate transition table based on + * the event. */ - Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table && - TupIsNull(oldslot))); - Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table && - TupIsNull(newslot))); - - if (!TupIsNull(oldslot) && - ((event == TRIGGER_EVENT_DELETE && delete_old_table) || - (event == TRIGGER_EVENT_UPDATE && update_old_table))) + if (!TupIsNull(oldslot)) { Tuplestorestate *old_tuplestore; - old_tuplestore = transition_capture->tcs_private->old_tuplestore; - - if (map != NULL) - { - AfterTriggersTableData *table = transition_capture->tcs_private; - TupleTableSlot *storeslot; - - storeslot = GetAfterTriggersStoreSlot(table, map->outdesc); - execute_attr_map_slot(map->attrMap, oldslot, storeslot); - tuplestore_puttupleslot(old_tuplestore, storeslot); - } - else - tuplestore_puttupleslot(old_tuplestore, oldslot); + old_tuplestore = AfterTriggerGetTransitionTable(event, + oldslot, + NULL, + transition_capture); + TransitionTableAddTuple(estate, transition_capture, relinfo, + oldslot, NULL, old_tuplestore); } - if (!TupIsNull(newslot) && - ((event == TRIGGER_EVENT_INSERT && insert_new_table) || - (event == TRIGGER_EVENT_UPDATE && update_new_table))) + + /* + * Capture the new tuple in the appropriate transition table based on + * the event. + */ + if (!TupIsNull(newslot)) { Tuplestorestate *new_tuplestore; - new_tuplestore = transition_capture->tcs_private->new_tuplestore; - - if (original_insert_tuple != NULL) - tuplestore_puttupleslot(new_tuplestore, - original_insert_tuple); - else if (map != NULL) - { - AfterTriggersTableData *table = transition_capture->tcs_private; - TupleTableSlot *storeslot; - - storeslot = GetAfterTriggersStoreSlot(table, map->outdesc); - execute_attr_map_slot(map->attrMap, newslot, storeslot); - tuplestore_puttupleslot(new_tuplestore, storeslot); - } - else - tuplestore_puttupleslot(new_tuplestore, newslot); + new_tuplestore = AfterTriggerGetTransitionTable(event, + NULL, + newslot, + transition_capture); + TransitionTableAddTuple(estate, transition_capture, relinfo, + newslot, + transition_capture->tcs_original_insert_tuple, + new_tuplestore); } /* -- 2.30.2 --tve4tfqa3b2wczcs Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v14-0002-Split-ExecUpdate-and-ExecDelete-in-reusable-piec.patch" Content-Transfer-Encoding: 8bit ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v3] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 80 +++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 95 +++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 68 ++++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 +++++++ src/include/pgstat.h | 16 ++++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 324 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index bb75ed1069b..9ec8070b2fc 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4372,6 +4372,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4382,6 +4392,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4391,6 +4411,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4401,6 +4431,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4411,6 +4451,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4421,6 +4471,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4430,6 +4490,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4440,6 +4510,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index e54018004db..2ca02a0354c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index bce3a2daa24..ce83b3171be 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -792,8 +792,18 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, rel = try_relation_open(relid, NoLock); else { + bits8 flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVAC; + + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -801,7 +811,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -904,7 +914,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -917,29 +926,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + bits8 flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index bc8c43b96aa..cec4c88980e 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,6 +17,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" @@ -366,6 +367,73 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !(flags & PGSTAT_REPORT_SKIPPED_ANY)) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_AUTOVAC) + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + } + else + { + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 9185a8e6b83..18aaa3996e1 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0118e970dda..ccfe3d9e02a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5667,6 +5667,38 @@ proname => 'pg_stat_get_total_autoanalyze_time', provolatile => 's', proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_total_autoanalyze_time' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 8e3549c3752..343081a7a25 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -707,6 +716,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVAC (1 << 2) /* skipped during autovacuum/autoanalyze */ +#define PGSTAT_REPORT_SKIPPED_ANY (PGSTAT_REPORT_SKIPPED_VACUUM | PGSTAT_REPORT_SKIPPED_ANALYZE) +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, bits8 flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 2b3cf6d8569..4ccfc43e903 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2293,13 +2301,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2348,13 +2364,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Fri__27_Mar_2026_16_35_49_+0900_rvNEESSISag912Sn-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v4] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++++++++++++ src/backend/catalog/system_views.sql | 8 ++ src/backend/commands/vacuum.c | 102 ++++++++++++++----- src/backend/utils/activity/pgstat_relation.c | 64 ++++++++++++ src/backend/utils/adt/pgstatfuncs.c | 24 +++++ src/include/catalog/pg_proc.dat | 32 ++++++ src/include/pgstat.h | 16 +++ src/test/regress/expected/rules.out | 24 +++++ 8 files changed, 335 insertions(+), 23 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..97cd5ea8d10 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -802,7 +819,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, return rel; /* - * Relation could not be opened, hence generate if possible a log + * Relation could not be opened hence generate if possible a log * informing on the situation. * * If the RangeVar is not defined, we do not have enough information to @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__13_Apr_2026_17_05_51_+0900_T4n8olu8FNI1Va/O-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v5] Track skipped vacuum and analyze activity per relation @ 2026-03-24 04:09 Yugo Nagata <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Yugo Nagata @ 2026-03-24 04:09 UTC (permalink / raw) This commit adds eight fields to the relation statistics that track the last time vacuum or analyze has been attempted but skipped due to lock unavailability, along with their counts: - last_skipped_vacuum - last_skipped_autovacuum - last_skipped_analyze - last_skipped_autoanalyze - skipped_vacuum_count - skipped_autovacuum_count - skipped_analyze_count - skipped_autoanalyze_count These field can help users confirm that autovacuum is actively attempting to run on a table that has not been vacuumed or analyzed for a long time, and that the lack of progress is due to repeated skips rather than inactivity. --- doc/src/sgml/monitoring.sgml | 88 ++++++ src/backend/catalog/system_views.sql | 8 + src/backend/commands/vacuum.c | 100 +++++-- src/backend/utils/activity/pgstat_relation.c | 64 +++++ src/backend/utils/adt/pgstatfuncs.c | 24 ++ src/include/catalog/pg_proc.dat | 32 +++ src/include/pgstat.h | 16 ++ .../isolation/expected/vacuum-skip-locked.out | 260 ++++++++++++++++-- .../isolation/specs/vacuum-skip-locked.spec | 41 +-- src/test/regress/expected/rules.out | 24 ++ 10 files changed, 601 insertions(+), 56 deletions(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b824552..a9b579d87a9 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4387,6 +4387,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_vacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual vacuum on this table was attempted but skipped due to + lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autovacuum</structfield> <type>timestamp with time zone</type> @@ -4397,6 +4407,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autovacuum</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a vacuum on this table by the autovacuum daemon was attempted + but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_analyze</structfield> <type>timestamp with time zone</type> @@ -4406,6 +4426,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_analyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time a manual analyze on this table was attempted but skipped due to + lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>last_autoanalyze</structfield> <type>timestamp with time zone</type> @@ -4416,6 +4446,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>last_skipped_autoanalyze</structfield> <type>timestamp with time zone</type> + </para> + <para> + Last time at which an analyze on this table by the autovacuum was + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>vacuum_count</structfield> <type>bigint</type> @@ -4426,6 +4466,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_vacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual vacuums on this table have been attempted but skipped + due to lock unavailability (not counting <command>VACUUM FULL</command>) + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autovacuum_count</structfield> <type>bigint</type> @@ -4436,6 +4486,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autovacuum_count</structfield> <type>bigint</type> + </para> + <para> + Number of times vacuums on this table by the autovacuum daemon have been + attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>analyze_count</structfield> <type>bigint</type> @@ -4445,6 +4505,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_analyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times manual analyzes on this table have been attempted but + skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>autoanalyze_count</structfield> <type>bigint</type> @@ -4455,6 +4525,16 @@ description | Waiting for a newly initialized WAL file to reach durable storage </para></entry> </row> + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>skipped_autoanalyze_count</structfield> <type>bigint</type> + </para> + <para> + Number of times analyzes on this table by the autovacuum daemon have + been attempted but skipped due to lock unavailability + </para></entry> + </row> + <row> <entry role="catalog_table_entry"><para role="column_definition"> <structfield>total_vacuum_time</structfield> <type>double precision</type> @@ -4510,6 +4590,14 @@ description | Waiting for a newly initialized WAL file to reach durable storage </tgroup> </table> + <note> + <para> + When a manual vacuum or analyze on a parent table in an inheritance or + partitioning hierarchy is skipped, the statistics are recorded only for + the parent table, not for its children. + </para> + </note> + </sect2> <sect2 id="monitoring-pg-stat-autovacuum-scores-view"> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c4670..f509fc7876b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -736,13 +736,21 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_mod_since_analyze(C.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(C.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(C.oid) as last_vacuum, + pg_stat_get_last_skipped_vacuum_time(C.oid) as last_skipped_vacuum, pg_stat_get_last_autovacuum_time(C.oid) as last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(C.oid) as last_skipped_autovacuum, pg_stat_get_last_analyze_time(C.oid) as last_analyze, + pg_stat_get_last_skipped_analyze_time(C.oid) as last_skipped_analyze, pg_stat_get_last_autoanalyze_time(C.oid) as last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(C.oid) as last_skipped_autoanalyze, pg_stat_get_vacuum_count(C.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(C.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(C.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(C.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(C.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(C.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(C.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(C.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(C.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(C.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(C.oid) AS total_analyze_time, diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 99d0db82ed7..df6526225e4 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -793,8 +793,25 @@ vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, rel = try_relation_open(relid, NoLock); else { + int flags = 0; rel = NULL; rel_lock = false; + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOVACUUM; + else + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + } + if ((options & VACOPT_ANALYZE) != 0) + { + if (AmAutoVacuumWorkerProcess()) + flags |= PGSTAT_REPORT_SKIPPED_AUTOANALYZE; + else + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + } + pgstat_report_skipped_vacuum_analyze(relid, flags); } /* if relation is opened, leave */ @@ -905,7 +922,6 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, Form_pg_class classForm; bool include_children; bool is_partitioned_table; - int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -918,29 +934,69 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; - relid = RangeVarGetRelidExtended(vrel->relation, - AccessShareLock, - rvr_opts, - NULL, NULL); - - /* - * If the lock is unavailable, emit the same log statement that - * vacuum_rel() and analyze_rel() would. - */ - if (!OidIsValid(relid)) + if (!(options & VACOPT_SKIP_LOCKED)) { - if (options & VACOPT_VACUUM) - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping vacuum of \"%s\" --- lock not available", - vrel->relation->relname))); - else - ereport(WARNING, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("skipping analyze of \"%s\" --- lock not available", + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + 0, NULL, NULL); + if (!OidIsValid(relid)) + return vacrels; + } + else + { + /* Get relid for reporting before taking a lock */ + relid = RangeVarGetRelid(vrel->relation, NoLock, false); + + if (!ConditionalLockRelationOid(relid, AccessShareLock)) + { + int flags = 0; + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", vrel->relation->relname))); - return vacrels; + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + + if ((options & VACOPT_VACUUM) != 0 && (options & VACOPT_FULL) == 0) + flags |= PGSTAT_REPORT_SKIPPED_VACUUM; + if ((options & VACOPT_ANALYZE) != 0) + flags |= PGSTAT_REPORT_SKIPPED_ANALYZE; + + pgstat_report_skipped_vacuum_analyze(relid, flags); + + return vacrels; + } + + /* + * Now that we have the lock, probe to see if the relation really + * exists or not. + */ + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid))) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + vrel->relation->relname))); + + /* Release useless lock */ + UnlockRelationOid(relid, AccessShareLock); + + return vacrels; + } } /* diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index b2ca28f83ba..532d9023f8c 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -17,12 +17,14 @@ #include "postgres.h" +#include "access/htup_details.h" #include "access/twophase_rmgr.h" #include "access/xact.h" #include "catalog/catalog.h" #include "utils/memutils.h" #include "utils/pgstat_internal.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/timestamp.h" @@ -367,6 +369,68 @@ pgstat_report_analyze(Relation rel, (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); } +/* + * Report that the table was skipped during vacuum or/and analyze. + */ +void +pgstat_report_skipped_vacuum_analyze(Oid relid, int flags) +{ + PgStat_EntryRef *entry_ref; + PgStatShared_Relation *shtabentry; + PgStat_StatTabEntry *tabentry; + TimestampTz ts; + HeapTuple classTup; + bool isshared; + + if (!pgstat_track_counts || !flags) + return; + + classTup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(classTup)) + return; /* somebody deleted the rel, forget it */ + isshared = ((Form_pg_class) GETSTRUCT(classTup))->relisshared; + ReleaseSysCache(classTup); + + /* Store the data in the table's hash table entry. */ + ts = GetCurrentTimestamp(); + + /* block acquiring lock for the same reason as pgstat_report_autovac() */ + entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, + isshared ? InvalidOid : MyDatabaseId, + relid, false); + + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + + if (flags & PGSTAT_REPORT_SKIPPED_VACUUM) + { + tabentry->last_skipped_vacuum_time = ts; + tabentry->skipped_vacuum_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOVACUUM) + { + tabentry->last_skipped_autovacuum_time = ts; + tabentry->skipped_autovacuum_count++; + } + + if (flags & PGSTAT_REPORT_SKIPPED_ANALYZE) + { + tabentry->last_skipped_analyze_time = ts; + tabentry->skipped_analyze_count++; + } + else if (flags & PGSTAT_REPORT_SKIPPED_AUTOANALYZE) + { + tabentry->last_skipped_autoanalyze_time = ts; + tabentry->skipped_autoanalyze_count++; + } + + pgstat_unlock_entry(entry_ref); + + /* see pgstat_report_vacuum() */ + pgstat_flush_io(false); + (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO); +} + /* * count a tuple insertion of n tuples */ diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1408de387ea..90a8968faa0 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -84,6 +84,18 @@ PG_STAT_GET_RELENTRY_INT64(mod_since_analyze) /* pg_stat_get_numscans */ PG_STAT_GET_RELENTRY_INT64(numscans) +/* pg_stat_get_skipped_analyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_analyze_count) + +/* pg_stat_get_skipped_autoanalyze_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autoanalyze_count) + +/* pg_stat_get_skipped_autovacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_autovacuum_count) + +/* pg_stat_get_skipped_vacuum_count */ +PG_STAT_GET_RELENTRY_INT64(skipped_vacuum_count) + /* pg_stat_get_tuples_deleted */ PG_STAT_GET_RELENTRY_INT64(tuples_deleted) @@ -170,6 +182,18 @@ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_vacuum_time) /* pg_stat_get_lastscan */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(lastscan) +/* pg_stat_get_last_skipped_analyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_analyze_time) + +/* pg_stat_get_last_skipped_autoanalyze_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autoanalyze_time) + +/* pg_stat_get_last_skipped_autovacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_autovacuum_time) + +/* pg_stat_get_last_skipped_vacuum_time */ +PG_STAT_GET_RELENTRY_TIMESTAMPTZ(last_skipped_vacuum_time) + /* pg_stat_get_stat_reset_time */ PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat_reset_time) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index fa9ae79082b..32debb34863 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5680,6 +5680,38 @@ proargmodes => '{o,o,o,o,o,o,o,o,o,o}', proargnames => '{oid,score,xid_score,mxid_score,vacuum_score,vacuum_insert_score,analyze_score,do_vacuum,do_analyze,for_wraparound}', prosrc => 'pg_stat_get_autovacuum_scores' }, +{ oid => '8142', descr => 'statistics: last skipped vacuum time for a table', + proname => 'pg_stat_get_last_skipped_vacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_vacuum_time' }, +{ oid => '8143', descr => 'statistics: last skipped auto vacuum time for a table', + proname => 'pg_stat_get_last_skipped_autovacuum_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autovacuum_time' }, +{ oid => '8144', descr => 'statistics: last skipped analyze time for a table', + proname => 'pg_stat_get_last_skipped_analyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_analyze_time' }, +{ oid => '8145', descr => 'statistics: last skipped auto analyze time for a table', + proname => 'pg_stat_get_last_skipped_autoanalyze_time', provolatile => 's', + proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', + prosrc => 'pg_stat_get_last_skipped_autoanalyze_time' }, +{ oid => '8146', descr => 'statistics: number of skipped vacuum for a table', + proname => 'pg_stat_get_skipped_vacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_vacuum_count' }, +{ oid => '8147', descr => 'statistics: number of skipped auto vacuum for a table', + proname => 'pg_stat_get_skipped_autovacuum_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autovacuum_count' }, +{ oid => '8148', descr => 'statistics: number of skipped analyzes for a table', + proname => 'pg_stat_get_skipped_analyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_analyze_count' }, +{ oid => '8149', descr => 'statistics: number of skipped auto analyzes for a table', + proname => 'pg_stat_get_skipped_autoanalyze_count', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_skipped_autoanalyze_count' }, { oid => '1936', descr => 'statistics: currently active backend IDs', proname => 'pg_stat_get_backend_idset', prorows => '100', proretset => 't', provolatile => 's', proparallel => 'r', prorettype => 'int4', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index dfa2e837638..98941f953d1 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -479,6 +479,15 @@ typedef struct PgStat_StatTabEntry TimestampTz last_autoanalyze_time; /* autovacuum initiated */ PgStat_Counter autoanalyze_count; + TimestampTz last_skipped_vacuum_time; /* user initiated vacuum */ + PgStat_Counter skipped_vacuum_count; + TimestampTz last_skipped_autovacuum_time; /* autovacuum initiated */ + PgStat_Counter skipped_autovacuum_count; + TimestampTz last_skipped_analyze_time; /* user initiated */ + PgStat_Counter skipped_analyze_count; + TimestampTz last_skipped_autoanalyze_time; /* autovacuum initiated */ + PgStat_Counter skipped_autoanalyze_count; + PgStat_Counter total_vacuum_time; /* times in milliseconds */ PgStat_Counter total_autovacuum_time; PgStat_Counter total_analyze_time; @@ -703,6 +712,13 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime); +/* flags for pgstat_flush_backend() */ +#define PGSTAT_REPORT_SKIPPED_VACUUM (1 << 0) /* vacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_ANALYZE (1 << 1) /* analyze is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOVACUUM (1 << 2) /* autovacuum is skipped */ +#define PGSTAT_REPORT_SKIPPED_AUTOANALYZE (1 << 3) /* autoanalyze is skipped */ +extern void pgstat_report_skipped_vacuum_analyze(Oid relid, int flags); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out index 99db281a159..e2cff175b7c 100644 --- a/src/test/isolation/expected/vacuum-skip-locked.out +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -1,6 +1,6 @@ Parsed test spec with 2 sessions -starting permutation: lock_share vac_specified commit +starting permutation: lock_share vac_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -10,8 +10,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -20,8 +34,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) + -starting permutation: lock_share analyze_specified commit +starting permutation: lock_share analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -31,8 +59,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -41,8 +83,22 @@ step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_analyze_specified commit +starting permutation: lock_share vac_analyze_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -52,8 +108,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_analyze_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_share vac_analyze_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -62,8 +132,22 @@ step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) + -starting permutation: lock_share vac_full_specified commit +starting permutation: lock_share vac_full_specified commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -73,8 +157,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_share vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_share vac_full_all_parts commit check_stat step lock_share: BEGIN; LOCK part1 IN SHARE MODE; @@ -83,8 +181,22 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_specified commit + +starting permutation: lock_access_exclusive vac_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -94,8 +206,22 @@ step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_all_parts commit + +starting permutation: lock_access_exclusive vac_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -104,8 +230,22 @@ step vac_all_parts: VACUUM (SKIP_LOCKED) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 0 +part2 | 1| 0| 0| 0 +(3 rows) -starting permutation: lock_access_exclusive analyze_specified commit + +starting permutation: lock_access_exclusive analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -115,8 +255,22 @@ step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 1 +part2 | 0| 0| 1| 0 +(3 rows) + -starting permutation: lock_access_exclusive analyze_all_parts commit +starting permutation: lock_access_exclusive analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -126,8 +280,22 @@ step commit: COMMIT; step analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_analyze_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 0| 0| 1| 0 +part2 | 0| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_analyze_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -137,8 +305,22 @@ step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 1| 0| 1 +part2 | 1| 0| 1| 0 +(3 rows) -starting permutation: lock_access_exclusive vac_analyze_all_parts commit + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -148,8 +330,22 @@ step commit: COMMIT; step vac_analyze_all_parts: <... completed> - -starting permutation: lock_access_exclusive vac_full_specified commit +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 1| 0 +part1 | 1| 0| 1| 0 +part2 | 1| 0| 1| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_specified commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -159,8 +355,22 @@ step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); -starting permutation: lock_access_exclusive vac_full_all_parts commit +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + + +starting permutation: lock_access_exclusive vac_full_all_parts commit check_stat step lock_access_exclusive: BEGIN; LOCK part1 IN ACCESS EXCLUSIVE MODE; @@ -169,3 +379,17 @@ step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; step commit: COMMIT; +step check_stat: + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); + +relname|vacuum_count|skipped_vacuum_count|analyze_count|skipped_analyze_count +-------+------------+--------------------+-------------+--------------------- +parted | 0| 0| 0| 0 +part1 | 0| 0| 0| 0 +part2 | 0| 0| 0| 0 +(3 rows) + diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec index 3fad6e1c92a..b0da75d4b6d 100644 --- a/src/test/isolation/specs/vacuum-skip-locked.spec +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -33,6 +33,15 @@ step commit COMMIT; } +step check_stat +{ + SELECT relname, + vacuum_count, skipped_vacuum_count, + analyze_count, skipped_analyze_count + FROM pg_stat_all_tables + WHERE relname IN ('parted', 'part1', 'part2'); +} + session s2 step vac_specified { VACUUM (SKIP_LOCKED) part1, part2; } step vac_all_parts { VACUUM (SKIP_LOCKED) parted; } @@ -43,19 +52,19 @@ step vac_analyze_all_parts { VACUUM (ANALYZE, SKIP_LOCKED) parted; } step vac_full_specified { VACUUM (SKIP_LOCKED, FULL) part1, part2; } step vac_full_all_parts { VACUUM (SKIP_LOCKED, FULL) parted; } -permutation lock_share vac_specified commit -permutation lock_share vac_all_parts commit -permutation lock_share analyze_specified commit -permutation lock_share analyze_all_parts commit -permutation lock_share vac_analyze_specified commit -permutation lock_share vac_analyze_all_parts commit -permutation lock_share vac_full_specified commit -permutation lock_share vac_full_all_parts commit -permutation lock_access_exclusive vac_specified commit -permutation lock_access_exclusive vac_all_parts commit -permutation lock_access_exclusive analyze_specified commit -permutation lock_access_exclusive analyze_all_parts commit -permutation lock_access_exclusive vac_analyze_specified commit -permutation lock_access_exclusive vac_analyze_all_parts commit -permutation lock_access_exclusive vac_full_specified commit -permutation lock_access_exclusive vac_full_all_parts commit +permutation lock_share vac_specified commit check_stat +permutation lock_share vac_all_parts commit check_stat +permutation lock_share analyze_specified commit check_stat +permutation lock_share analyze_all_parts commit check_stat +permutation lock_share vac_analyze_specified commit check_stat +permutation lock_share vac_analyze_all_parts commit check_stat +permutation lock_share vac_full_specified commit check_stat +permutation lock_share vac_full_all_parts commit check_stat +permutation lock_access_exclusive vac_specified commit check_stat +permutation lock_access_exclusive vac_all_parts commit check_stat +permutation lock_access_exclusive analyze_specified commit check_stat +permutation lock_access_exclusive analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_analyze_specified commit check_stat +permutation lock_access_exclusive vac_analyze_all_parts commit check_stat +permutation lock_access_exclusive vac_full_specified commit check_stat +permutation lock_access_exclusive vac_full_all_parts commit check_stat diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4f..9b2075d3373 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1835,13 +1835,21 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_mod_since_analyze(c.oid) AS n_mod_since_analyze, pg_stat_get_ins_since_vacuum(c.oid) AS n_ins_since_vacuum, pg_stat_get_last_vacuum_time(c.oid) AS last_vacuum, + pg_stat_get_last_skipped_vacuum_time(c.oid) AS last_skipped_vacuum, pg_stat_get_last_autovacuum_time(c.oid) AS last_autovacuum, + pg_stat_get_last_skipped_autovacuum_time(c.oid) AS last_skipped_autovacuum, pg_stat_get_last_analyze_time(c.oid) AS last_analyze, + pg_stat_get_last_skipped_analyze_time(c.oid) AS last_skipped_analyze, pg_stat_get_last_autoanalyze_time(c.oid) AS last_autoanalyze, + pg_stat_get_last_skipped_autoanalyze_time(c.oid) AS last_skipped_autoanalyze, pg_stat_get_vacuum_count(c.oid) AS vacuum_count, + pg_stat_get_skipped_vacuum_count(c.oid) AS skipped_vacuum_count, pg_stat_get_autovacuum_count(c.oid) AS autovacuum_count, + pg_stat_get_skipped_autovacuum_count(c.oid) AS skipped_autovacuum_count, pg_stat_get_analyze_count(c.oid) AS analyze_count, + pg_stat_get_skipped_analyze_count(c.oid) AS skipped_analyze_count, pg_stat_get_autoanalyze_count(c.oid) AS autoanalyze_count, + pg_stat_get_skipped_autoanalyze_count(c.oid) AS skipped_autoanalyze_count, pg_stat_get_total_vacuum_time(c.oid) AS total_vacuum_time, pg_stat_get_total_autovacuum_time(c.oid) AS total_autovacuum_time, pg_stat_get_total_analyze_time(c.oid) AS total_analyze_time, @@ -2346,13 +2354,21 @@ pg_stat_sys_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, @@ -2401,13 +2417,21 @@ pg_stat_user_tables| SELECT relid, n_mod_since_analyze, n_ins_since_vacuum, last_vacuum, + last_skipped_vacuum, last_autovacuum, + last_skipped_autovacuum, last_analyze, + last_skipped_analyze, last_autoanalyze, + last_skipped_autoanalyze, vacuum_count, + skipped_vacuum_count, autovacuum_count, + skipped_autovacuum_count, analyze_count, + skipped_analyze_count, autoanalyze_count, + skipped_autoanalyze_count, total_vacuum_time, total_autovacuum_time, total_analyze_time, -- 2.43.0 --Multipart=_Mon__27_Apr_2026_20_32_07_+0900_jUJUG74bF5asMQ6P-- ^ permalink raw reply [nested|flat] 431+ messages in thread
* [PATCH v47 8/9] Error out any process that would block at REPACK @ 2026-03-25 19:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 431+ messages in thread From: Álvaro Herrera @ 2026-03-25 19:35 UTC (permalink / raw) Any process waiting on REPACK to release its lock would actually cause it to deadlock when it tries to upgrade its lock to AEL, losing all work done to that point. We avoid this by teaching the deadlock detector to raise an error when this condition is detected. --- src/backend/commands/repack.c | 52 ++++++++---- src/backend/storage/lmgr/deadlock.c | 15 ++++ src/include/storage/proc.h | 6 +- src/test/modules/injection_points/Makefile | 1 + .../expected/repack_deadlock.out | 63 ++++++++++++++ src/test/modules/injection_points/meson.build | 1 + .../specs/repack_deadlock.spec | 83 +++++++++++++++++++ 7 files changed, 202 insertions(+), 19 deletions(-) create mode 100644 src/test/modules/injection_points/expected/repack_deadlock.out create mode 100644 src/test/modules/injection_points/specs/repack_deadlock.spec diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 9c807f75d71..3acb2e1d2ba 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -276,6 +276,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) /* Determine the lock mode to use. */ lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0); + /* + * If in concurrent mode, set the PROC_IN_CONCURRENT_REPACK flag. This + * makes the deadlock checker cause anyone that would conflict with us + * to error out. It's important to set this flag ahead of actually locking + * the relation; it won't of course affect anyone until we do have a lock + * that others can conflict with. + */ + if ((params.options & CLUOPT_CONCURRENT) != 0) + MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK; + /* * If a single relation is specified, process it and we're done ... unless * the relation is a partitioned table, in which case we fall through. @@ -476,11 +486,8 @@ RepackLockLevel(bool concurrent) * If indexOid is InvalidOid, the table will be rewritten in physical order * instead of index order. * - * Note that, in the concurrent case, the function releases the lock at some - * point, in order to get AccessExclusiveLock for the final steps (i.e. to - * swap the relation files). To make things simpler, the caller should expect - * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The - * AccessExclusiveLock is kept till the end of the transaction.) + * On return, OldHeap is closed but locked with AccessExclusiveLock - the lock + * will be released at end of the transaction. * * 'cmd' indicates which command is being executed, to be used for error * messages. @@ -512,10 +519,12 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, /* * Make sure we're not in a transaction block. * - * The reason is that repack_setup_logical_decoding() could deadlock - * if there's an XID already assigned. It would be possible to run in - * a transaction block if we had no XID, but this restriction is - * simpler for users to understand and we don't lose anything. + * The reason is that repack_setup_logical_decoding() could wait + * indefinitely for our XID to complete. (The deadlock detector would + * not recognize it because we'd be waiting for ourselves, i.e. no + * real lock conflict.) It would be possible to run in a transaction + * block if we had no XID, but this restriction is simpler for users + * to understand and we don't lose anything. */ PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)"); @@ -998,10 +1007,8 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose, * Note that the worker has to wait for all transactions with XID * already assigned to finish. If some of those transactions is * waiting for a lock conflicting with ShareUpdateExclusiveLock on our - * table (e.g. it runs CREATE INDEX), we can end up in a deadlock. - * Not sure this risk is worth unlocking/locking the table (and its - * clustering index) and checking again if it's still eligible for - * REPACK CONCURRENTLY. + * table (e.g. it runs CREATE INDEX), it should encounter ERROR in the + * deadlock checking code. */ start_repack_decoding_worker(tableOid); @@ -3090,7 +3097,16 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); /* - * Tuples and pages of the old heap will be gone, but the heap will stay. + * Now that we have all access-exclusive locks on all relations, we no + * longer want other processes to error out when trying to acquire a + * conflicting lock. Therefore, unset our flag. + */ + MyProc->statusFlags &= ~PROC_IN_CONCURRENT_REPACK; + + /* + * Tuples and pages of the old heap will be gone, but the heap itself will + * stay. In order for predicate locks to continue to work, convert them + * to relation-level locks. We do this both for table and indexes. */ TransferPredicateLocksToHeapRelation(OldHeap); foreach_ptr(RelationData, index, indexrels) @@ -3287,9 +3303,11 @@ start_repack_decoding_worker(Oid relid) /* * The decoding setup must be done before the caller can have XID assigned - * for any reason, otherwise the worker might end up in a deadlock, - * waiting for the caller's transaction to end. Therefore wait here until - * the worker indicates that it has the logical decoding initialized. + * for any reason, otherwise the worker might end up waiting for the + * caller's transaction to end. (Deadlock detector does not consider this + * a conflict because the worker is in the same locking group as the + * backend that launched it.) Therefore wait here until the worker + * indicates that it has the logical decoding initialized. */ ConditionVariablePrepareToSleep(&shared->cv); for (;;) diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c index b8962d875b6..3d3ec0da111 100644 --- a/src/backend/storage/lmgr/deadlock.c +++ b/src/backend/storage/lmgr/deadlock.c @@ -620,6 +620,21 @@ FindLockCycleRecurseMember(PGPROC *checkProc, proc->statusFlags & PROC_IS_AUTOVACUUM) blocking_autovacuum_proc = proc; + /* + * Similarly, if we note that we're blocked by some process + * running REPACK (CONCURRENTLY), just fail. That process + * is going to upgrade its lock at some point, and it would + * be inappropriate for any other process to cause that + * to fail. + */ + if (checkProc == MyProc && + proc->statusFlags & PROC_IN_CONCURRENT_REPACK) + ereport(ERROR, + errcode(ERRCODE_OBJECT_IN_USE), + errmsg("could not wait for concurrent REPACK"), + errdetail("Process %d waits for REPACK running on process %d", + MyProc->pid, proc->pid)); + /* We're done looking at this proclock */ break; } diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 1dad125706e..8ad9718f3d6 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -69,10 +69,12 @@ struct XidCache #define PROC_AFFECTS_ALL_HORIZONS 0x20 /* this proc's xmin must be * included in vacuum horizons * in all databases */ +#define PROC_IN_CONCURRENT_REPACK 0x40 /* REPACK (CONCURRENTLY) */ -/* flags reset at EOXact */ +/* flags reset at EOXact. A bit of a misnomer ... */ #define PROC_VACUUM_STATE_MASK \ - (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND) + (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND | \ + PROC_IN_CONCURRENT_REPACK) /* * Xmin-related flags. Make sure any flags that affect how the process' Xmin diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index 2cd7d87c533..f7663859fe2 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -15,6 +15,7 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress ISOLATION = basic \ inplace \ repack \ + repack_deadlock \ repack_toast \ syscache-update-pruned \ heap_lock_update diff --git a/src/test/modules/injection_points/expected/repack_deadlock.out b/src/test/modules/injection_points/expected/repack_deadlock.out new file mode 100644 index 00000000000..a86e4767536 --- /dev/null +++ b/src/test/modules/injection_points/expected/repack_deadlock.out @@ -0,0 +1,63 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock add_column wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_deadlock USING INDEX repack_deadlock_pkey; + <waiting ...> +step add_column: + alter table repack_deadlock add column noise text; + <waiting ...> +step add_column: <... completed> +ERROR: could not wait for concurrent REPACK +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_deadlock'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_deadlock ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_deadlock; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 1 +(1 row) + +i|j +-+- +1|1 +2|2 +3|3 +4|4 +(4 rows) + +count +----- + 4 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index a414abb924b..1cd88d6db65 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -46,6 +46,7 @@ tests += { 'basic', 'inplace', 'repack', + 'repack_deadlock', 'repack_toast', 'syscache-update-pruned', 'heap_lock_update', diff --git a/src/test/modules/injection_points/specs/repack_deadlock.spec b/src/test/modules/injection_points/specs/repack_deadlock.spec new file mode 100644 index 00000000000..9d23a6588c2 --- /dev/null +++ b/src/test/modules/injection_points/specs/repack_deadlock.spec @@ -0,0 +1,83 @@ +# Test REPACK with a concurrent transaction that would cause a deadlock +setup +{ + CREATE EXTENSION injection_points; + + CREATE TABLE repack_deadlock(i int PRIMARY KEY, j int); + INSERT INTO repack_deadlock(i, j) VALUES (1, 1), (2, 2), (3, 3), (4, 4); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j int); + CREATE TABLE data_s2(i int, j int); +} + +teardown +{ + DROP TABLE repack_deadlock; + DROP EXTENSION injection_points; + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_deadlock USING INDEX repack_deadlock_pkey; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_deadlock'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_deadlock ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_deadlock; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +# Change the existing data. UPDATE changes both key and non-key columns. Also +# update one row twice to test whether tuple version generated by this session +# can be found. +step add_column +{ + alter table repack_deadlock add column noise text; +} + +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + add_column + wakeup_before_lock + check1 -- 2.47.3 --2fxmamo6mu2qbxgv Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="v47-0009-Teach-snapshot-builder-to-skip-transactions-runn.patch" ^ permalink raw reply [nested|flat] 431+ messages in thread
end of thread, other threads:[~2026-03-25 19:35 UTC | newest] Thread overview: 431+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2022-02-02 19:58 [PATCH v14 1/3] Add API of sorts for transition table handling in trigger.c Alvaro Herrera <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v3] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v4] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-24 04:09 [PATCH v5] Track skipped vacuum and analyze activity per relation Yugo Nagata <[email protected]> 2026-03-25 19:35 [PATCH v47 8/9] Error out any process that would block at REPACK Álvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox